/*
 * Copyright 2020-2021 Huawei Technologies Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package org.edgegallery.atp.service;

import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.edgegallery.atp.constant.Constant;
import org.edgegallery.atp.constant.ErrorCode;
import org.edgegallery.atp.interfaces.filter.AccessTokenFilter;
import org.edgegallery.atp.model.PageResult;
import org.edgegallery.atp.model.task.AnalysisResult;
import org.edgegallery.atp.model.task.TaskRequest;
import org.edgegallery.atp.model.task.TestCaseStatusReq;
import org.edgegallery.atp.model.task.testscenarios.TaskTestCase;
import org.edgegallery.atp.model.task.testscenarios.TaskTestScenario;
import org.edgegallery.atp.model.task.testscenarios.TaskTestSuite;
import org.edgegallery.atp.model.testcase.TestCase;
import org.edgegallery.atp.model.testscenario.TestScenario;
import org.edgegallery.atp.model.testsuite.TestSuite;
import org.edgegallery.atp.model.user.User;
import org.edgegallery.atp.repository.task.TaskRepository;
import org.edgegallery.atp.repository.testcase.TestCaseRepository;
import org.edgegallery.atp.repository.testscenario.TestScenarioRepository;
import org.edgegallery.atp.repository.testsuite.TestSuiteRepository;
import org.edgegallery.atp.schedule.testcase.TestCaseManagerImpl;
import org.edgegallery.atp.utils.CommonUtil;
import org.edgegallery.atp.utils.FileChecker;
import org.edgegallery.atp.utils.exception.FileNotExistsException;
import org.edgegallery.atp.utils.exception.IllegalRequestException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

@Service("TaskService")
public class TaskServiceImpl implements TaskService {
    private static final Logger LOGGER = LoggerFactory.getLogger(TaskServiceImpl.class);

    private static final String BASE_PATH = "/report/";

    private static final String TASK_FROM_DB_NULL = "get task from db is null, taskId: %s";

    @Autowired
    TaskRepository taskRepository;

    @Autowired
    TestCaseRepository testCaseRepository;

    @Autowired
    TestCaseManagerImpl testCaseManager;

    @Autowired
    TestScenarioRepository testScenarioRepository;

    @Autowired
    TestSuiteRepository testSuiteRepository;

    @Value("${servicecomb.uploads.directory}")
    private String uploadPath;

    @Override
    public TaskRequest createTask(MultipartFile file) {
        String taskId = CommonUtil.generateId();
        File tempFile = FileChecker.check(file, taskId);
        try {
            String filePath = tempFile.getCanonicalPath();
            Map<String, String> packageInfo = CommonUtil.getPackageInfo(filePath);
            Map<String, String> context = AccessTokenFilter.CONTEXT.get();
            TaskRequest task = TaskRequest.builder().setId(taskId).setStatus(Constant.ATP_CREATED)
                .setUser(new User(context.get(Constant.USER_ID), context.get(Constant.USER_NAME)))
                .setPackagePath(filePath).setAppName(packageInfo.get(Constant.APP_NAME))
                .setAppVersion(packageInfo.get(Constant.APP_VERSION))
                .setProviderId(packageInfo.get(Constant.PROVIDER_ID)).build();
            task.setCreateTime(taskRepository.getCurrentDate());
            taskRepository.insert(task);
            LOGGER.info("create task successfully.");
            return task;
        } catch (IOException e) {
            LOGGER.error("create task {} failed, file name is: {}", taskId, tempFile.getName());
            throw new IllegalRequestException(ErrorCode.FILE_IO_EXCEPTION_MSG, ErrorCode.FILE_IO_EXCEPTION, null);
        } finally {
            AccessTokenFilter.deleteContext();
        }
    }

    @Override
    public TaskRequest runTask(String taskId, List<String> scenarioIdList) throws FileNotExistsException {
        scenarioIdsEmptyValidation(scenarioIdList);
        initTestScenarios(scenarioIdList);
        TaskRequest task = taskRepository.findByTaskIdAndUserId(taskId, null);
        CommonUtil.checkEntityNotFound(task, String.format(TASK_FROM_DB_NULL, taskId), Constant.TASK_ID);
        taskStatusValidation(task);

        Map<String, String> context = AccessTokenFilter.CONTEXT.get();
        task.setTestScenarios(initTestScenarios(scenarioIdList));
        task.setAccessToken(context.get(Constant.ACCESS_TOKEN));
        task.setStatus(Constant.WAITING);
        taskRepository.update(task);

        String filePath = task.getPackagePath();
        testCaseManager.executeTestCase(task, filePath);

        LOGGER.info("run task successfully.");
        AccessTokenFilter.deleteContext();
        return task;
    }

    @Override
    public ResponseEntity<Boolean> deleteTaskById(String taskId) {
        Map<String, String> context = AccessTokenFilter.CONTEXT.get();
        String userId = context.get(Constant.USER_ID);
        TaskRequest task = taskRepository.findByTaskIdAndUserId(taskId, userId);
        if (null != task) {
            taskRepository.deleteTaskById(taskId, userId);
            CommonUtil.deleteFile(task.getPackagePath());
            if (StringUtils.isNotEmpty(task.getReportPath())) {
                CommonUtil.deleteFile(uploadPath.concat(task.getReportPath()));
            }
        }
        AccessTokenFilter.deleteContext();
        return ResponseEntity.ok(Boolean.TRUE);
    }

    @Override
    public String uploadReport(String taskId, MultipartFile file) throws FileNotExistsException {
        TaskRequest task = taskRepository.findByTaskIdAndUserId(taskId, null);
        CommonUtil.checkEntityNotFound(task, String.format(TASK_FROM_DB_NULL, taskId), Constant.TASK_ID);

        String path = BASE_PATH.concat(taskId).concat(".pdf");
        String filePath = uploadPath.concat(path);
        try {
            FileChecker.createFile(filePath);
            File result = new File(filePath);
            file.transferTo(result);
            task.setReportPath(path);
            taskRepository.update(task);
            return path;
        } catch (IOException e) {
            LOGGER.error("upload report failed.");
            throw new IllegalRequestException(ErrorCode.FILE_IO_EXCEPTION_MSG, ErrorCode.FILE_IO_EXCEPTION, null);
        }
    }

    @Override
    public ResponseEntity<List<TaskRequest>> getAllTasks(String userId, String appName, String status,
        String providerId, String appVersion) {
        List<TaskRequest> response = taskRepository.findTaskByUserId(userId, appName, status, providerId, appVersion);
        LOGGER.info("get all task successfully.");
        return ResponseEntity.ok(response);
    }

    @Override
    public PageResult<TaskRequest> getAllTasksByPagination(String userId, String appName, String status,
        String providerId, String appVersion, int limit, int offset) {
        PageResult<TaskRequest> pageResult = new PageResult<TaskRequest>(offset, limit);
        pageResult.setTotal(taskRepository.countTotal(userId, appName, status, providerId, appVersion));
        pageResult.setResults(
            taskRepository.getAllWithPagination(limit, offset, null, appName, status, providerId, appVersion));
        LOGGER.info("query all tasks by pagination successfully.");
        return pageResult;
    }

    @Override
    public TaskRequest getTaskById(String taskId) throws FileNotExistsException {
        TaskRequest task = taskRepository.findByTaskIdAndUserId(taskId, null);
        CommonUtil.checkEntityNotFound(task, String.format(TASK_FROM_DB_NULL, taskId), Constant.TASK_ID);
        LOGGER.info("get task by id successfully.");
        return task;
    }

    @Override
    public Map<String, List<String>> batchDelete(List<String> taskIds) {
        Map<String, List<String>> response = taskRepository.batchDelete(taskIds);
        LOGGER.info("batch delete successfully.");
        return response;
    }

    @Override
    public AnalysisResult taskAnalysis() {
        Date curTime = taskRepository.getCurrentDate();
        Calendar calendar = Calendar.getInstance();
        int date = curTime.getDate();
        calendar.setTime(curTime);
        // first day of month
        calendar.add(Calendar.DATE, -date);
        Date firstDayOfMonth = calendar.getTime();
        int oneMonthDays = firstDayOfMonth.getDate();
        calendar.add(Calendar.DATE, -oneMonthDays);
        int twoMonthDays = calendar.getTime().getDate();
        calendar.add(Calendar.DATE, -twoMonthDays);
        int threeMonthDays = calendar.getTime().getDate();
        calendar.add(Calendar.DATE, -threeMonthDays);
        int fourMonthDays = calendar.getTime().getDate();
        calendar.add(Calendar.DATE, -fourMonthDays);
        int fiveMonthDays = calendar.getTime().getDate();
        // get days of * month
        int last2Days = oneMonthDays + twoMonthDays;
        int last3Days = last2Days + threeMonthDays;
        int last4Days = last3Days + fourMonthDays;
        int last5Days = last4Days + fiveMonthDays;

        List<TaskRequest> response = taskRepository.findTaskByUserId(null, null, null, null, null);
        AnalysisResult analysisResult = new AnalysisResult();
        response.forEach(task -> {
            if (task.getCreateTime().getYear() == curTime.getYear() && task.getCreateTime().getMonth() == curTime
                .getMonth()) {
                analysisResult.increaseCurrentMonth();
            } else {
                // just consider day, not hours
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
                try {
                    int day = (int) ((dateFormat.parse(dateFormat.format(firstDayOfMonth)).getTime() - dateFormat
                        .parse(dateFormat.format(task.getCreateTime())).getTime()) / (1000 * 3600 * 24));
                    if (day < oneMonthDays) {
                        analysisResult.increaseOneMonthAgo();
                    } else if (oneMonthDays <= day && day < last2Days) {
                        analysisResult.increaseTwoMonthAgo();
                    } else if (last2Days <= day && day < last3Days) {
                        analysisResult.increaseThreeMonthAgo();
                    } else if (last3Days <= day && day < last4Days) {
                        analysisResult.increaseFourMonthAgo();
                    } else if (last4Days <= day && day < last5Days) {
                        analysisResult.increaseFiveMonthAgo();
                    }
                } catch (ParseException e) {
                    LOGGER.error("data format parse failed.");
                }
            }
        });

        analysisResult.setTotal();
        return analysisResult;
    }

    @Override
    public ResponseEntity<Boolean> modifyTestCaseStatus(List<TestCaseStatusReq> testCaseStatusList, String taskId) {
        TaskRequest task = taskRepository.findByTaskIdAndUserId(taskId, null);
        List<TaskTestScenario> testScenarioList = task.getTestScenarios();

        testCaseStatusList.forEach(testCaseStatus -> {
            testScenarioList.forEach(testScenario -> {
                if (testScenario.getId().equals(testCaseStatus.getTestScenarioId())) {
                    List<TaskTestSuite> testSuiteList = testScenario.getTestSuites();
                    updateStatus(testSuiteList, testCaseStatus);
                }
            });
        });

        confirmTaskStatus(task);
        taskRepository.update(task);
        return ResponseEntity.ok(Boolean.TRUE);
    }

    /**
     * confirm task total status.
     *
     * @param task task info
     */
    private void confirmTaskStatus(TaskRequest task) {
        String status = Constant.SUCCESS;
        for (TaskTestScenario testScenario : task.getTestScenarios()) {
            List<TaskTestSuite> testSuiteList = testScenario.getTestSuites();
            if (CollectionUtils.isNotEmpty(testSuiteList)) {
                for (TaskTestSuite testSuite : testSuiteList) {
                    List<TaskTestCase> testCases = testSuite.getTestCases();
                    status = setStatus(testCases);
                }
            }
        }
        task.setStatus(status);
    }

    /**
     * set test case total status.
     *
     * @param testCases task test case list
     * @return test case status
     */
    private String setStatus(List<TaskTestCase> testCases) {
        String status = Constant.SUCCESS;
        if (CollectionUtils.isNotEmpty(testCases)) {
            for (TaskTestCase testCase : testCases) {
                status = setStatus(testCase, status);
            }
        }
        return status;
    }

    private String setStatus(TaskTestCase testCase, String status) {
        if (Constant.RUNNING.equals(testCase.getResult())) {
            status = Constant.RUNNING;
        } else {
            if (!Constant.RUNNING.equals(status)) {
                status = Constant.FAILED.equals(testCase.getResult()) ? Constant.FAILED : status;
            }
        }
        return status;
    }

    /**
     * init test scenario info list.
     *
     * @param scenarioIdList scenarioIdList
     * @return task test scenario list
     */
    private List<TaskTestScenario> initTestScenarios(List<String> scenarioIdList) {
        List<TaskTestScenario> result = new ArrayList<TaskTestScenario>();
        scenarioIdList.forEach(scenarioId -> {
            TestScenario testScenario = testScenarioRepository.getTestScenarioById(scenarioId);
            CommonUtil.checkParamEmpty(testScenario, String.format("scenarioId %s not exists", scenarioId),
                "scenarioId: ".concat(scenarioId));

            TaskTestScenario scenario = new TaskTestScenario();
            scenario.setId(scenarioId);
            scenario.setNameCh(testScenario.getNameCh());
            scenario.setNameEn(testScenario.getNameEn());
            scenario.setLabel(testScenario.getLabel());

            List<TestSuite> testSuiteList = testSuiteRepository.getAllTestSuites(null, null, scenarioId);
            List<TaskTestSuite> testSuites = new ArrayList<TaskTestSuite>();
            testSuiteList.forEach(testSuite -> {
                TaskTestSuite tempTestSuite = new TaskTestSuite();
                tempTestSuite.setId(testSuite.getId());
                tempTestSuite.setNameCh(testSuite.getNameCh());
                tempTestSuite.setNameEn(testSuite.getNameEn());

                List<TestCase> testCaseList = testCaseRepository.findAllTestCases(null, null, null, testSuite.getId());
                List<TaskTestCase> testCases = new ArrayList<TaskTestCase>();
                testCaseList.stream().forEach(testCase -> testCases.add(constructTaskTestCase(testCase)));
                tempTestSuite.setTestCases(testCases);
                testSuites.add(tempTestSuite);
            });
            scenario.setTestSuites(testSuites);
            result.add(scenario);
        });
        return result;
    }

    /**
     * construct task test case.
     *
     * @param testCase test case
     * @return task test case info
     */
    private TaskTestCase constructTaskTestCase(TestCase testCase) {
        TaskTestCase tempTestCase = new TaskTestCase();
        tempTestCase.setId(testCase.getId());
        tempTestCase.setNameCh(testCase.getNameCh());
        tempTestCase.setNameEn(testCase.getNameEn());
        tempTestCase.setDescriptionCh(testCase.getDescriptionCh());
        tempTestCase.setDescriptionEn(testCase.getDescriptionEn());
        tempTestCase.setType(testCase.getType());
        tempTestCase.setResult(Constant.WAITING);
        tempTestCase.setReason(Constant.EMPTY);
        return tempTestCase;
    }

    /**
     * update test case status.
     *
     * @param testSuiteList test suite list
     * @param testCaseStatus test case status
     */
    private void updateStatus(List<TaskTestSuite> testSuiteList, TestCaseStatusReq testCaseStatus) {
        testSuiteList.forEach(testSuite -> {
            if (testSuite.getId().equals(testCaseStatus.getTestSuiteId())) {
                List<TaskTestCase> testCaseList = testSuite.getTestCases();
                testCaseList.forEach(testCase -> {
                    if (testCase.getId().equals(testCaseStatus.getTestCaseId())) {
                        testCase.setResult(testCaseStatus.getResult());
                        testCase.setReason(testCaseStatus.getReason());
                    }
                });
            }
        });
    }

    /**
     * task status validation, can not be running.
     *
     * @param task task info
     */
    private void taskStatusValidation(TaskRequest task) {
        if (Constant.RUNNING.equals(task.getStatus())) {
            LOGGER.error("this task already in running.");
            throw new IllegalRequestException(ErrorCode.TASK_IS_RUNNING_MSG, ErrorCode.TASK_IS_RUNNING, null);
        }
    }

    /**
     * check scenarioIdList not empty.
     *
     * @param scenarioIdList scenarioIdList
     * @throws FileNotExistsException FileNotExistsException
     */
    private void scenarioIdsEmptyValidation(List<String> scenarioIdList) throws FileNotExistsException {
        if (CollectionUtils.isEmpty(scenarioIdList)) {
            LOGGER.error("scenarioIdList is empty.");
            throw new IllegalRequestException(String.format(ErrorCode.PARAM_IS_NULL_MSG, "scenarioIdList"),
                ErrorCode.PARAM_IS_NULL, new ArrayList<String>(Arrays.asList("scenarioIdList")));
        }
    }
}
